/*
 * Copyright 2008-2009 Adam Tacy <adam.tacy AT gmail.com>
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.adamtacy.client.ui.effects;

import java.util.Iterator;
import java.util.Vector;

import org.adamtacy.client.ui.NEffectPanel;
import org.adamtacy.client.ui.effects.events.EffectCompletedEvent;
import org.adamtacy.client.ui.effects.events.EffectCompletedHandler;
import org.adamtacy.client.ui.effects.events.EffectInterruptedEvent;
import org.adamtacy.client.ui.effects.events.EffectInterruptedHandler;
import org.adamtacy.client.ui.effects.events.EffectPausedEvent;
import org.adamtacy.client.ui.effects.events.EffectPausedHandler;
import org.adamtacy.client.ui.effects.events.EffectResumedEvent;
import org.adamtacy.client.ui.effects.events.EffectResumedHandler;
import org.adamtacy.client.ui.effects.events.EffectStartingEvent;
import org.adamtacy.client.ui.effects.events.EffectStartingHandler;
import org.adamtacy.client.ui.effects.events.EffectSteppingEvent;
import org.adamtacy.client.ui.effects.events.EffectSteppingHandler;
import org.adamtacy.client.ui.effects.events.HasAllEffectEventHandlers;
import org.adamtacy.client.ui.effects.impl.browsers.EffectImplementation;
import org.adamtacy.client.ui.effects.transitionsphysics.EaseInOutTransitionPhysics;
import org.adamtacy.client.ui.effects.transitionsphysics.TransitionPhysics;

import com.google.gwt.animation.client.Animation;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Timer;

/**
 * A class that provides the basics of an effect.
 * 
 * From v4 a new style of setting properties is introduced.
 * 
 * From v3 of GWT-FX this class is based on the GWT Animation class to keep
 * divergence from GWT itself as minimal as possible. To maintain the previous
 * API, this class extends Animation with a number of fields and methods.
 * 
 * It is an abstract class that provides a fair amount of functionality, but
 * leaves the implementation of the
 * 
 * @author adam
 * 
 */
public abstract class Effect extends Animation implements
		HasAllEffectEventHandlers {

	/**
	 *  Indicates if start > end in play parameters (different to inverse)
	 */
	protected boolean backwards = false;

	/**
	 * A list of effects that are chained to start after this effect finishes.
	 */
	protected Vector<Effect> chainedEffects;

	/**
	 * Handler for chained effects
	 */
	HandlerRegistration chainedEffectsHandlers;

	/**
	 * The physical position of the effect.
	 * A value between 0,0 and 1,0.  It differs from currentEffectPosition in that 
	 * it shows the affect of TransitionPhysics applied to it.
	 */
	protected double currentEffectPhysicalPosition = 0.0;
	
	/**
	 * The current position of the effect.
	 * A value between 0.0 and 1.0.  It differs from currentEffectPhysicalPosition in that
	 * no TransitionPhysics is applied to it, i.e. it is typically a linear progression.
	 */
	protected double currentEffectPosition = 0.0;

	/**
	 * Default duration of the effect
	 */
	protected final double DEFAULT_EFFECT_LENGTH = 2.0;

	// Removed by enhancement 104 in favour of a Vector effectElements
	// protected Element effectElement;
	/**
	 * A vector of elements that this effect is applied to.
	 * Generally this will contain one element but if containing more, then the 
	 * effect is applied in the same position to all elements.
	 */
	protected Vector<Element> effectElements = new Vector<Element>();

	/**
	 * Has the effect finished?
	 */
	protected boolean effectFinished = false;

	/**
	 * The duration of this effect.
	 * Change it by using the setDuration method.
	 */
	protected double effectLengthSeconds = DEFAULT_EFFECT_LENGTH;

	/**
	 * Has the effect started?
	 */
	protected boolean effectStarted = false;

	/**
	 * Need this as we're not extending a widget and so have no access to
	 * that HandlerManager.
	 */
	HandlerManager handlerManager = new HandlerManager(DEFAULT_EFFECT_LENGTH);

	/**
	 * Is the effect interruptable, i.e. can we pause and cancel it?
	 */
	protected boolean interruptable = true;

	/**
	 * Is the effect inverted
	 */
	protected boolean inverted = false;

	/**
	 * Details if the effect has been initialised yet or not. Updated by either
	 * the init(NEffectPanel thePanel) method, or through the play() method.
	 */
	protected boolean isInitialised = false;

	/**
	 * Is the effect to loop after completion?
	 */
	boolean looping = false;

	/**
	 * Is the effect paused?
	 */
	boolean paused = false;

	/** 
	 * 
 	 * Store the real effect end point - usually this will be at position 1.0
 	 * unless overridden by the user (see method interpolate for use of this
	 * variable).
	 *  
	 **/
	double requestedEffectEnd = 1.0;

	/**
	 *  Store the real effect start point - usually this will be at position 0.0
	 * unless overridden by the user (see method interpolate for use of this
	 * variable).
	 **/ 
	double requestedEffectStart = 0.0;

	/**
	 * A timer that can be used to delay the start of this effect.
	 */
	Timer startDelay;

	/**
	 * The effectPanel the effect may be attached to. From v0.5 this is not
	 * necessary for  effects.
	 */
	protected NEffectPanel thePanel;

	/**
	 * The particular TransitionPhysics that will be applied to the effect progression.
	 */
	protected TransitionPhysics transition;
	
	
	/**
	 * Adds an EffectCompletedHandler.
	 * To remove store the HandlerRegistration object and call the remove method on it
	 */
	public HandlerRegistration addEffectCompletedHandler(
			EffectCompletedHandler handler) {
		return addHandler(handler, EffectCompletedEvent.getType());
	}

	/**
	 * Adds an Element to be managed by this effect. See enhancement 104:
	 * http://code.google.com/p/gwt-fx/issues/detail?id=104
	 */
	public void addEffectElement(Element el) {
		if(effectElements==null) setEffectElement(el);
		else effectElements.add(el);
	}
	
	/**
	 * Adds a Vector of elements in bulk to the effect
	 * @param elements
	 */
	public void addEffectElements(Vector<Element> elements){
		if(effectElements==null){
			setEffectElement(elements.get(0));
			elements.remove(0);
		}
		effectElements.addAll(elements);
	}

	/**
	 * Adds an EffectInterruptedHandler.
	 * To remove store the HandlerRegistration object and call the remove method on it
	 */
	public HandlerRegistration addEffectInterruptedHandler(
			EffectInterruptedHandler handler) {
		return addHandler(handler, EffectInterruptedEvent.getType());
	}

	/**
	 * Adds an EffectPausedHandler.
	 * To remove store the HandlerRegistration object and call the remove method on it
	 */
	public HandlerRegistration addEffectPausedHandler(
			EffectPausedHandler handler) {
		return addHandler(handler, EffectPausedEvent.getType());
	}

	/**
	 * Adds an EffectResumedHandler.
	 * To remove store the HandlerRegistration object and call the remove method on it
	 */
	public HandlerRegistration addEffectResumedHandler(
			EffectResumedHandler handler) {
		return addHandler(handler, EffectResumedEvent.getType());
	}

	/**
	 * Adds an EffectStartedHandler.
	 * To remove store the HandlerRegistration object and call the remove method on it
	 */
	public HandlerRegistration addEffectStartingHandler(
			EffectStartingHandler handler) {
		return addHandler(handler, EffectStartingEvent.getType());
	}

	/**
	 * Adds an EffectSteppingHandler.
	 * To remove store the HandlerRegistration object and call the remove method on it
	 */
	public HandlerRegistration addEffectSteppingHandler(
			EffectSteppingHandler handler) {
		return addHandler(handler, EffectSteppingEvent.getType());
	}

	/**
	 * Adds this handler to the widget.
	 * 
	 * @param handler
	 *            the handler
	 * @param type
	 *            the event type
	 * @return {@link HandlerRegistration} used to remove the handler
	 */
	protected final <H extends EventHandler> HandlerRegistration addHandler(
			final H handler, GwtEvent.Type<H> type) {
		return ensureHandlers().addHandler(type, handler);
	}

	/**
	 * Cancels the effect only if it is interruptable. Updated due to issue 139
	 * to cancel start delay if one is set.
	 */
	@Override
	public void cancel() {
		if (interruptable) {
			if (this.startDelay != null)
				startDelay.cancel();
			super.cancel();
		}
	}

	/**
	 * Adds an effect to the chain. Effects are linked together in the init
	 * method.
	 * 
	 * @param effect
	 *            The effect to chain.
	 */
	public void chain(final Effect effect) {
		if (chainedEffects == null) {
			chainedEffects = new Vector<Effect>();
		}
		chainedEffects.add(effect);
	}

	/**
	 * Ensures the existence of the handler manager.
	 * 
	 * @return the handler manager
	 */
	HandlerManager ensureHandlers() {
		return handlerManager == null ? handlerManager = new HandlerManager(
				this) : handlerManager;
	}

	/**
	 * Fires an event. Usually used when passing an event from one source to
	 * another.
	 * 
	 * @param event
	 *            the event
	 */
	public void fireEvent(GwtEvent<?> event) {
		if (handlerManager != null) {
			handlerManager.fireEvent(event);
		}
	}

	/**
	 * Returns the effect duration in seconds.
	 */
	public double getDuration() {
		return effectLengthSeconds;
	}

	/**
	 * Returns the first element this effect is applied to, unless this effect
	 * is added to an {@link org.adamtacy.client.ui.NEffectPanel}, in which case
	 * that panel's Element is returned.
	 * See defect #146 to ensure null is returned if no effect element set yet.
	 */
	public Element getEffectElement() {
		Element toReturn = null;
		if (!effectElements.isEmpty())effectElements.get(0);
		if (thePanel != null)
			toReturn = thePanel.getElement();
		return toReturn;
	}

	/**
	 * Returns the NEffectPanel that holds the component and effect - not required from v0.4
	 * @return thePanel that holds a widget and effects
	 */
	public NEffectPanel getEffectPanel() {
		return thePanel;
	}

	/**
	 * Get the event handler manager.
	 * @return
	 */
	public HandlerManager getHandlers() {
		return handlerManager;
	}

	/**
	 * Gets the layout definition that IE requires to use filters. Only has
	 * meaning for IE.
	 */
	public String getIELayoutDefinition() {
		return EffectImplementation.getIELayoutDefinition();
	}

	/**
	 * Returns the current progress of this effect: a value somewhere between
	 * 0.0 and 1.0.
	 */
	public double getProgress() {
		return currentEffectPhysicalPosition;
	}

	/**
	 * Returns the interpolated progress of this effect.
	 */
	public double getProgressInterpolated() {
		return currentEffectPosition;
	}

	/**
	 * Returns the current Transition Physics Model used by this effect.
	 */
	public TransitionPhysics getTransitionPhysics() {
		return transition;
	}

	/**
	 * Initialise the effect
	 */
	public void init() {
		// Set a default transition if none provided
		if (transition == null)
			transition = new EaseInOutTransitionPhysics();

		if (chainedEffects != null) {
			// Start chaining the effects together
			// First initialise them all
			if (thePanel != null)
				for (Iterator<Effect> it = chainedEffects.iterator(); it
						.hasNext();) {
					it.next().init(thePanel);
				}
			// Add an effect completed handler for this effect to fire all
			// chained effects
			chainedEffectsHandlers = this
					.addEffectCompletedHandler(new EffectCompletedHandler() {
						public void onEffectCompleted(EffectCompletedEvent event) {
							for (Iterator<Effect> it = chainedEffects
									.iterator(); it.hasNext();) {
								it.next().play();
							}
						}
					});
		}
		// Now we can set up the effect - this is defined in concrete implementations of this class
		setUpEffect();
	}

	/**
	 * Initialises an effect on an {@link org.adamtacy.client.ui.NEffectPanel}.
	 * 
	 * @param thePanel
	 *            The NEffectPanel on which this effect is being applied.
	 */
	public void init(NEffectPanel thePanel) {
		this.thePanel = thePanel;
		init();
		isInitialised = true;
	}

	/**
	 * Interpolates the progress of this effect based on the Transition Physics.
	 * Subclasses should fire
	 * {@link org.adamtacy.client.ui.effects.events.EffectSteppingEvent} with
	 * the interpolated value.
	 */
	@Override
	protected double interpolate(double progress) {
		currentEffectPhysicalPosition = interpolateWithoutUpdate(progress);
		return interpolateFinish(currentEffectPhysicalPosition);
	}

	/**
	 * Completes the interpolate functionality. Zak asked what is returned if
	 * transition is null - transition shouldn't be null, but good catch. If
	 * transition is null at this point, just return the value passed in.
	 * 
	 * @param progress
	 * @return
	 */
	private double interpolateFinish(double progress) {
		double val = progress;
		if (transition != null) {
			val = transition.getAnimationPosition(progress);
			if (inverted)
				val = 1 - val;
		}
		return val;
	}

	protected double interpolateWithoutUpdate(double progress) {
		if (transition == null)
			return 1.0;
		if (!backwards) {
			progress = progress + requestedEffectStart;
			if (progress > requestedEffectEnd)
				progress = requestedEffectEnd;
		} else {
			progress = requestedEffectStart - progress;
			if (progress < requestedEffectEnd)
				progress = requestedEffectEnd;
		}
		if ((progress == 1.0)) {
			if (inverted)
				return 0.0;
			else
				return 1.0;
		}
		if (progress == 0.0) {
			if (inverted)
				return 1.0;
			else
				return 0.0;
		}
		return progress;
	}

	/**
	 * Inverts an effect. Currently not available for Sequential composite
	 * effects.
	 */
	public void invert() {
		assert (!(this instanceof SequentialCompositeEffect));
		inverted = !inverted;
	}

	/**
	 * Returns a boolean value indicating if the effect has finished.
	 */
	public boolean isFinished() {
		return effectFinished;
	}

	/**
	 * Returns a boolean value to indicate if the effect is interruptable.
	 */
	public boolean isInterruptable() {
		return interruptable;
	}

	/**
	 * Returns a boolean value to indicate if the effect is inverted.
	 */
	public boolean isInverted() {
		return inverted;
	}

	/**
	 * Returns a boolean value to indicate if the effect is to be looped.
	 */
	public boolean isLooped() {
		return looping;
	}

	/**
	 * Determine if an NEffect has started.
	 * 
	 * @return boolean value indicating if NEffect has started or not.
	 */
	public boolean isStarted() {
		return effectStarted;
	}

	/**
	 * Called immediately after the animation is cancelled. Fires
	 * {@link org.adamtacy.client.ui.effects.events.EffectPausedEvent} or
	 * {@link org.adamtacy.client.ui.effects.events.EffectInterruptedEvent} as
	 * appropriate.
	 * 
	 * Does not call <code>super.onCancel()</code>. See issues 82 and 83:
	 * http://code.google.com/p/gwt-fx/issues/detail?id=82
	 * http://code.google.com/p/gwt-fx/issues/detail?id=83
	 */
	@Override
	protected void onCancel() {
		if (paused)
			fireEvent(new EffectPausedEvent());
		else
			fireEvent(new EffectInterruptedEvent());
	}

	/**
	 * Called immediately after the animation completes. Fires
	 * {@link org.adamtacy.client.ui.effects.events.EffectCompletedEvent}. If
	 * this effect is looping, calls {@link #play(double, double)}.
	 */
	@Override
	protected void onComplete() {
		super.onComplete();
		effectFinished = true;
		fireEvent(new EffectCompletedEvent());
		if (looping) {
			DeferredCommand.addCommand(new Command() {
				public void execute() {
					play(0.0, 1.0);
				}
			});
		}
	}

	/**
	 * Called immediately before the animation starts. Fires
	 * {@link org.adamtacy.client.ui.effects.events.EffectStartingEvent}.
	 * 
	 * Does not call <code>super.onStart()</code>. See issue 95:
	 * http://code.google.com/p/gwt-fx/issues/detail?id=95
	 */
	@Override
	protected void onStart() {
		effectFinished = false;
		effectStarted = true;
		fireEvent(new EffectStartingEvent());
	}

	/**
	 * Called when the animation should be updated.
	 * 
	 * The value of progress is between 0.0 and 1.0 inclusively (unless you
	 * override the {@link #interpolate(double)} method to provide a wider range
	 * of values). You can override {@link #onStart()} and {@link #onComplete()}
	 * to perform setup and tear down procedures.
	 * 
	 * Note that onUpdate events are now triggered by a call to the interpolate
	 * method whereas before they were through the direct update methods - this
	 * needs to be done since the update method in GWT Animation is private.
	 */
	@Override
	protected void onUpdate(double progress) {
		currentEffectPosition = progress;
	}

	/**
	 * Pauses an effect and sets paused variable to true
	 */
	public void pause() {
		cancel();
		paused = true;
	}

	/**
	 * Plays the effect using the registered start and end positions. If no
	 * positions are set they are defaulted to 0.0 and 1.0 respectively.
	 */
	public void play() {
		if (!isInitialised) {
			init();
			isInitialised = true;
		}
		double timeRatio = this.requestedEffectEnd - this.requestedEffectStart;
		if (timeRatio < 0)
			timeRatio = -timeRatio;
		run((int) ((effectLengthSeconds * 1000) * timeRatio));
	}

	/**
	 * Plays the effect from specified start and end positions. If the start
	 * position is greater than the end position, this effect is played in
	 * reverse.
	 * 
	 * Note that the portion of effect selected will run at the same tempo as
	 * the whole effect, but the firing of any postEffect handlers will not
	 * occur until the whole duration is complete.
	 * 
	 * For example, an effect with duration 2 seconds, but calling play(0.0,
	 * 0.5) will appear to viewer to complete in 1 second, but will not fire any
	 * postEvents until after 2 seconds.
	 * 
	 * @param start
	 *            The requested start position.
	 * @param end
	 *            The requested end position.
	 */
	public void play(double start, double end) {
		if (start > end)
			backwards = true;
		else
			backwards = false;
		requestedEffectStart = start;
		requestedEffectEnd = end;
		play();
	}

	/**
	 * Plays the effect after a specified delay from specified start and end
	 * positions.
	 * 
	 * @param start
	 *            Start position.
	 * @param end
	 *            End position.
	 * @param afterDelay
	 *            The delay in milliseconds.
	 */
	public void play(final double start, final double end, int afterDelay) {
		startDelay = new Timer() {
			@Override
			public void run() {
				play(start, end);
			}
		};
		startDelay.schedule(afterDelay);
	}

	/**
	 * Plays the effect after a specified delay, starting at position 0.0 and
	 * going to 1.0.
	 * 
	 * @param afterDelay
	 *            The delay in milliseconds.
	 */
	public void play(int afterDelay) {
		startDelay = new Timer() {
			@Override
			public void run() {
				play();
			}
		};
		startDelay.schedule(afterDelay);
	}

	/**
	 * Make sure that effectElements has at least one component if 
	 * @throws Exception 
	 */
	protected void registerEffectElement() {
		if (effectElements.isEmpty()) {
			if(thePanel!=null) {
				Element el = thePanel.getWidget().getElement();
				effectElements.add(el);						
			} else GWT.log("No element available to register for the effect");
		}
	}

	/**
	 * Cancels this effect and tears it down.
	 */
	public void remove() {
		cancel();
		tearDownEffect();
	}

	/**
	 * Removes and tears down this effect. This should only be used when this
	 * effect has been added to an Element directly. If this effect is added to
	 * an {@link org.adamtacy.client.ui.NEffectPanel}, it must be removed using
	 * {@link org.adamtacy.client.ui.NEffectPanel#removeEffect(Effect)}.
	 * 
	 * See issue 77: http://code.google.com/p/gwt-fx/issues/detail?id=77
	 */
	public void removeEffect() {
		removeEffect(true);
	}

	/**
	 * Removes this effect, optionally tearing it down. This should only be used
	 * when this effect has been added to an Element directly. If this effect is
	 * added to an {@link org.adamtacy.client.ui.NEffectPanel}, it must be
	 * removed using
	 * {@link org.adamtacy.client.ui.NEffectPanel#removeEffect(Effect)}.
	 * 
	 * See issue 77: http://code.google.com/p/gwt-fx/issues/detail?id=77
	 * 
	 * @param undoEffect
	 *            Whether or not to tear down this effect.
	 */
	public void removeEffect(boolean undoEffect) {
		assert (thePanel == null);
		if (undoEffect)
			tearDownEffect();
		effectElements = null;
	}
	
	
	public void removeEffectElement(Element el){
		removeEffectElement(el, true);
	}
	
	public void removeEffectElement(Element el, boolean undoEffect){
		effectElements.remove(el);
		if(undoEffect)tearDownEffect(el);
	}
	
	public void removeEffectElements(Vector<Element> els){
		removeEffectElements(els, true);
	}
	
	public void removeEffectElements(Vector<Element> els, boolean undoEffect){
		effectElements.removeAll(els);
		if(undoEffect)tearDownEffect(els);
	}
	

	/**
	 * Resets this effect.
	 */
	public void reset() {
		onUpdate(0.0);
		this.effectFinished = false;
		this.effectStarted = false;
		this.paused = false;
	}

	/**
	 * Plays this effect backwards from its current progress to 0.0. Fires
	 * {@link org.adamtacy.client.ui.effects.events.EffectResumedEvent}.
	 */
	public void resumeBackwards() {
		resumeBackwards(0.0);
	}

	/**
	 * Plays this effect backwards from its current progress to a specified
	 * point. Fires
	 * {@link org.adamtacy.client.ui.effects.events.EffectResumedEvent}.
	 * 
	 * @param end
	 *            Progress point at which to end.
	 */
	public void resumeBackwards(double end) {
		paused = false;
		play(getProgress(), end);
		fireEvent(new EffectResumedEvent());
	}

	/**
	 * Plays this effect from its current progress to 1.0. Fires
	 * {@link org.adamtacy.client.ui.effects.events.EffectResumedEvent}.
	 */
	public void resumeForwards() {
		resumeForwards(1.0);
	}

	/**
	 * Plays this effect from its current progress to a specified point. Fires
	 * {@link org.adamtacy.client.ui.effects.events.EffectResumedEvent}.
	 * 
	 * @param end
	 *            Progress point at which to end.
	 */
	public void resumeForwards(double end) {
		paused = false;
		this.play(getProgress(), end);
		fireEvent(new EffectResumedEvent());
	}

	/**
	 * Sets this effect's duration in seconds.
	 * 
	 * @param duration
	 *            The duration in seconds.
	 */
	public void setDuration(double duration) {
		effectLengthSeconds = duration;
	}

	/**
	 * Sets the Element to which this effect will be applied. This will remove
	 * all ties this effect has to other Elements. If you would like to add
	 * multiple Elements, use {@link #addEffectElement(Element)}.
	 * 
	 * @param element
	 *            The Element to which this effect should be applied.
	 */
	public void setEffectElement(Element element) {
		if (thePanel != null)
			throw new RuntimeException(
					"Trying to change effectElement when an EffectPanel is in place");
		effectElements = new Vector<Element>();
		effectElements.add(element);
		isInitialised = false;
	}

	/**
	 * Sets the layout definition that IE requires to use filters. Only required
	 * for IE.
	 */
	public void setIELayoutDefinition(String id, String val) {
		EffectImplementation.setIELayoutDefinition(id, val);
	}

	public void setLooping(boolean val) {
		looping = val;
	}

	/**
	 * Sets the effect's position. Fires appropriate events.
	 * 
	 * @param position
	 *            The position to be set.
	 *            
	 * See defect #145 which fixes how events are fired.
	 */
	public void setPosition(double position) {
		if (!this.isInitialised) {
			init();
			isInitialised = true;
		}
		double val = interpolate(position);
		onUpdate(val);
		if (position == 0.0)
			fireEvent(new EffectStartingEvent());
		else if (position == 1.0)
			fireEvent(new EffectCompletedEvent());
		else
			fireEvent(new EffectSteppingEvent());
	}

	/**
	 * Sets the
	 * {@link org.adamtacy.client.ui.effects.transitionsphysics.TransitionPhysics}
	 * to be used with this effect.
	 * 
	 * @param transition
	 *            The transition physics to use.
	 */
	public void setTransitionType(TransitionPhysics transition) {
		this.transition = transition;
		if (chainedEffects != null) {
			for (Iterator<Effect> it = chainedEffects.iterator(); it.hasNext();) {
				Effect theEffect = it.next();
				if (!theEffect.equals(this))
					theEffect.setTransitionType(transition);
			}
		}
	}

	/**
	 * Sets up this effect. Implemented by subclasses, for example a Show effect
	 * may wish to hide the Element before starting.
	 */
	public abstract void setUpEffect();


	/**
	 * Tears down this whole effect for all elements controlled by this effect
	 */
	public void tearDownEffect(){
		tearDownEffect(effectElements);
	}
	
	/**
	 * Tears down the effect for a set of elements
	 * @param els
	 */
	public void tearDownEffect(Vector<Element> els){
		if ((els==null)||(els.isEmpty())) return;
		for (Iterator<Element> iter = els.iterator(); iter.hasNext();){
			Element e = iter.next();
			tearDownEffect(e);
		} 
	}
	
	/**
	 * Tears down this effect. Called when this effect is removed. Implemented
	 * by subclasses, for example, the Move effect needs to fix the style
	 * properties that it sets up.
	 */
	public abstract void tearDownEffect(Element el);

	/**
	 * Unchains all effects that have previously been chained.
	 */
	public void unchain() {
		if (chainedEffects != null) {
			if (chainedEffectsHandlers != null)
				chainedEffectsHandlers.removeHandler();
			chainedEffects = null;
			chainedEffectsHandlers = null;
		}
		isInitialised = false;
	}
}
